package com.component;



import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author fanxiao  2022/3/30
 * @since 1.0.0
 */
public class CandidatesProxy<T> implements InvocationHandler {

	private Map<Method, MethodInvoker> methodCache;

	private Map<Method, Object> annotationValueCache = new ConcurrentHashMap<>(128);

	private Class<T> serviceInterface;


	private MethodInvoker cachedInvoker(Method method) throws Throwable {
		return methodCache.computeIfAbsent(method, m -> {
			if (m.isDefault()) {
				throw new RuntimeException("Can not support default method : "
						+ method.getName() + " in interface : " + serviceInterface.getName());
			} else {
				annotationValueCache.computeIfAbsent(method, m1 -> {
					Get annotation = m.getAnnotation(Get.class);
					if (annotation != null) {
						return annotation.value();
					}
					return "";
				});
				return new PlainMethodInvoker(method, annotationValueCache);
			}
		});
	}

	interface MethodInvoker {
		Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
	}

	private static class PlainMethodInvoker implements MethodInvoker {

		private Method method;

		private Map<Method, Object> annotationValueCache = new ConcurrentHashMap<>(128);

		public PlainMethodInvoker(Method method, Map<Method, Object> map) {
			this.method = method;
			this.annotationValueCache = map;
		}

		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			return annotationValueCache.getOrDefault(method, null);
		}
	}


	public CandidatesProxy(Class<T> serviceInterface, Map<Method, MethodInvoker> map) {
		this.serviceInterface = serviceInterface;
		this.methodCache = map;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		if (Object.class.equals(method.getDeclaringClass())){
			return method.invoke(proxy, args);
		} else {
			return cachedInvoker(method).invoke(proxy, method, args);
		}
	}
}